﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ArgusPCLib.Reflection
{
	public static class TypeInitializer
	{
		public static T CreateInstance<T>()
		{
			return TypeInitializer.CreateInstance<T>(true);
		}

		public static T CreateInstance<T>(bool includeNonPublic)
		{
			return (T)TypeInitializer.CreateInstance(typeof(T), includeNonPublic);
		}

		public static object CreateInstance(Type type)
		{
			return TypeInitializer.CreateInstance(type, true);
		}

		public static object CreateInstance(Type type, bool includeNonPublic)
		{
			Exception exception;
			try { return Activator.CreateInstance(type, includeNonPublic); }
			catch (Exception e) { exception = e; }

			IEnumerable<MethodInfo> mis = type.GetRuntimeMethods();
			List<MethodInfo> constructors = new List<MethodInfo>();
			foreach (MethodInfo mi in mis)
			{
				if (!mi.IsPublic && !includeNonPublic)
					continue;

				if (mi.IsConstructor)
					constructors.Add(mi);

				if (!mi.ReturnType.Is(type))
					continue;

				ParameterInfo[] pis = mi.GetParameters();
				if (pis.Length == 0)
					return mi.Invoke(null, null);
			}
			mis = null;

			ParameterInfo[] parameterInfos;
			MethodInfo ci = TypeInitializer.GetBestConstructor(type, constructors, out parameterInfos);
			if (ci == null)
				throw exception;
			object[] args = new object[parameterInfos.Length];
			for (int i = 0; i < args.Length; i++)
			{
				args[i] = TypeInitializer.CreateInstance(parameterInfos[i].ParameterType, includeNonPublic);
			}

			return ci.Invoke(null, args);
		}

		private static MethodInfo GetBestConstructor(Type type, IEnumerable<MethodInfo> constructorInfos, out ParameterInfo[] parameterInfos)
		{
			MethodInfo RetVal = null;
			int index = -1;
			int structParams = int.MaxValue;
			int Params = int.MaxValue;
			parameterInfos = null;

			foreach (MethodInfo ci in constructorInfos)
			{
				ParameterInfo[] pis = ci.GetParameters();
				if (pis.Length < 1)
					return ci;

				int sP = 0;
				int p = 0;
				bool Continue = false;
				foreach (ParameterInfo pi in pis)
				{
					if (pi.ParameterType == type)
					{
						Continue = true;
						break;
					}
					if (pi.ParameterType.GetTypeInfo().IsValueType)
						sP++;
					else
						p++;
				}

				if (Continue == true)
					continue;

				if (p < Params || (p == Params && sP < structParams))
				{
					RetVal = ci;
					structParams = sP;
					Params = p;
					parameterInfos = pis;
				}
			}

			return RetVal;
		}
	}
}
